package com.xunji.standard.zip;

import com.xunji.standard.cache.CodeBookCache;
import com.xunji.standard.common.Constant;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.CompressionLevel;
import net.lingala.zip4j.model.enums.CompressionMethod;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 增强版压缩文件解压工具类
 * 支持格式：ZIP/7Z/TAR/GZIP/BZIP2/TAR.GZ
 * 支持加密压缩包（ZIP/7Z）
 * 依赖：commons-compress + commons-io + zip4j
 */
public class AdvancedCompressionUtils {

    public static Logger log = LoggerFactory.getLogger(AdvancedCompressionUtils.class);
    public static String ZIP_TYPE = "zip,war,7z,tar,gz,gzip,bz2,bzip2,tgz,tar.gz";


    // 使用示例
    public static void main(String[] args) throws IOException {
        //recursionDecompress("D:\\xunji\\test\\xunji-st.zip", "123".toCharArray());

        //List<File> files = unzipAndGetContents("D:\\xunji\\st-standard\\caseFile_copy\\xunji-st.zip", "D:\\xunji\\st-standard\\caseFile_copy\\", null);
        //System.out.println(files);
        //AdvancedCompressionUtils.zip("D:\\MyJava\\temp", "D:\\MyJava\\temp.zip");

/*        File file = new File("D:\\xunji\\test\\xunji-st\\第一层\\xunji-standard\\bin\\start.sh");
        String absolutePath = file.getAbsolutePath();
        System.out.println(absolutePath);*/
    }

    /**
     * 递归解压函数，只解压，删除压缩包本身,遇到密码或者解压失败，则停止
     *
     * @param sourceFile 压缩文件路径
     * @param password   密码（可选，null 表示无密码）
     */
    public static void recursionDecompress(String sourceFile, char[] password) {
        File file = new File(sourceFile);
        if (!file.exists()) {
            log.error("找不到文件，文件路径：{}", sourceFile);
            return;
        }
        if (file.isFile()) { // 是文件
            String afterZipPath = foZip(file, password);
            if (afterZipPath != null) {
                File dFile = new File(afterZipPath);
                recursionDecompress(dFile.getAbsolutePath(), password); // 递归函数
            }

        } else if (file.isDirectory()) { // 是文件夹,这里存在一个递归，文件夹里面含有文件夹
            File[] files = file.listFiles();
            if (files != null) {
                for (File f : files) {
                    if (f.isFile()) {
                        String extension = FilenameUtils.getExtension(f.getAbsolutePath()).toLowerCase();
                        if (ZIP_TYPE.contains(extension)) { // 是压缩文件
                            String afterZipPath = foZip(f, password);
                            if (afterZipPath != null) {
                                File dFile = new File(afterZipPath);
                                recursionDecompress(dFile.getAbsolutePath(), password); // 递归函数
                            }
                        }
                    } else if (f.isDirectory()) {
                        recursionDecompress(f.getAbsolutePath(), password); // 递归函数
                    }
                }
            }
        }
    }


    private static String foZip(File file, char[] password) {
        int decompress = decompress(file.getAbsolutePath(), file.getParent(), password);
        if (decompress != 0) {
            log.error("文件路径：{}解压失败，可能原因：文件损坏、需要密码、密码不正确", file.getAbsolutePath());
            return null;
        }
        // 解压成功，进行下一步解压
        log.info("文件路径：{} 解压成功", file.getAbsolutePath());
        // 删除压缩包
        file.delete(); // 删除压缩包
        return file.getParent(); // 因为不确定压缩包解压后的文件名，所以此处返回文件上级文件夹
    }


    /**
     * 解压任意压缩文件（自动检测格式）
     *
     * @param sourceFile 压缩文件路径
     * @param targetDir  解压目标目录
     * @param password   密码（可选，null 表示无密码）
     * @return 0 解压成功 1需要密码 2格式不支持 3文件找不到 4 解压失败
     */
    public static int decompress(String sourceFile, String targetDir, char[] password) {
        File file = new File(sourceFile);
        if (!file.exists()) {
            log.error("找不到文件，path：{}", sourceFile);
            return 3; // 文件找不到
        }
        int decompress;

        // 先尝试解压到临时目录
        decompress = doDecompressWithPassword(sourceFile, System.getProperty("java.io.tmpdir"), password);

        if (decompress != 0) {
            // 初始密码解压失败，尝试使用密码本
            List<String> passwordList = CodeBookCache.get(Constant.CODE_BOOK);
            if (passwordList != null && !passwordList.isEmpty()) {
                log.info("文件：{}，尝试使用密码本解压", sourceFile);
                for (String passwordStr : passwordList) {
                    log.info("密码本：{}", passwordStr);
                    char[] passwordArray = passwordStr.toCharArray();
                    // 先尝试解压到临时目录
                    int decompressTemp = doDecompressWithPassword(sourceFile, System.getProperty("java.io.tmpdir"), passwordArray);
                    if (decompressTemp == 0) {
                        decompress = doDecompressWithPassword(sourceFile, targetDir, passwordArray);
                        if (decompress == 0) {
                            log.info("文件：{}，解压成功！", sourceFile);
                            return decompress;
                        }
                    }
                }
            }
        } else {
            // 密码解压成功，真实解压
            decompress = doDecompressWithPassword(sourceFile, targetDir, password);
        }
        log.info("文件：{}，解压成功！", sourceFile);
        return decompress;
    }

    private static int doDecompressWithPassword(String sourceFile, String targetDir, char[] password) {
        int decompress = 0;
        try {
            String extension = FilenameUtils.getExtension(sourceFile).toLowerCase();
            Path targetPath = Paths.get(targetDir);
            Files.createDirectories(targetPath);

            switch (extension) {
                case "zip":
                case "war":
                    decompressZipWithPassword(sourceFile, targetPath, password);
                    break;
                case "7z":
                    decompress7zWithPassword(sourceFile, targetPath, password);
                    break;
                case "tar":
                    decompressArchive(sourceFile, targetPath);
                    break;
                case "gz":
                case "gzip":
                    decompressGzip(sourceFile, targetPath);
                    break;
                case "bz2":
                case "bzip2":
                    decompressBzip2(sourceFile, targetPath);
                    break;
                case "tgz":
                case "tar.gz":
                    decompressTarGz(sourceFile, targetPath);
                    break;
                default:
                    throw new UnsupportedOperationException("Unsupported format: " + extension);
            }
        } catch (Exception e) {
            if (e.getMessage().contains("Wrong password") || e.getMessage().contains("requires password") || e.getMessage().contains("without a password") || e.getMessage().contains("Compressed data is corrupt")) {
                log.error("密码解压失败或密码不正确，path：{}", sourceFile);
                decompress = 1; // 需要密码
            } else if (e.getMessage().contains("not supported") || e.getMessage().contains("Unsupported format")) {
                log.error("不支持的文件格式，path：{}", sourceFile);
                decompress = 2; //
            } else {
                log.error("文件：{}，解压失败！", sourceFile);
                decompress = 4;
            }
        }
        return decompress;
    }

    /**
     * 解压ZIP文件（支持加密）
     */
    private static void decompressZipWithPassword(String sourceFile, Path targetDir, char[] password) throws IOException {
        // 智能检测并设置字符编码
        ZipFile zipFile = new ZipFile(sourceFile);
        Charset detectedCharset = detectZipCharset(sourceFile);
        zipFile.setCharset(detectedCharset);

        log.debug("使用字符编码: {} 解压ZIP文件: {}", detectedCharset.name(), sourceFile);

        // 检测加密并设置密码
        if (zipFile.isEncrypted()) {
            if (password == null || password.length == 0) {
                throw new IOException("Encrypted ZIP requires password");
            }
            zipFile.setPassword(password);
        }

        // 解决中文文件名问题
        List<FileHeader> headers = zipFile.getFileHeaders();
        for (FileHeader header : headers) {
            String fileName = header.getFileName();
            String originalFileName = fileName; // 保存原始文件名用于日志
            if (fileName != null) {
                // 统一路径分隔符
                fileName = fileName.replace("\\", "/");

                // 如果检测到可能的编码问题，尝试修复
                fileName = fixFileNameIfNecessary(fileName, detectedCharset);

                // 如果修复后的文件名仍然包含乱码，尝试多种编码转换
                if (containsGarbledChars(fileName)) {
                    fileName = tryMultipleEncodingFix(fileName, detectedCharset);
                }

                // 记录文件名修复情况
//                if (!originalFileName.equals(fileName)) {
                    log.info("修复文件名: '{}' -> '{}'", originalFileName, fileName);
//                }
            }

            Path entryPath = targetDir.resolve(fileName);
            safeCreateFile(entryPath, header.isDirectory());

            if (!header.isDirectory()) {
                try (InputStream is = zipFile.getInputStream(header)) {
                    Files.copy(is, entryPath, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }

    /**
     * 智能检测ZIP文件的字符编码
     *
     * @param sourceFile ZIP文件路径
     * @return 检测到的字符编码
     */
    private static Charset detectZipCharset(String sourceFile) {
        List<Charset> charsetCandidates = new ArrayList<>();

        // 添加候选编码，按照优先级排序
        charsetCandidates.add(Charset.forName("GBK"));           // GBK (Windows中文默认)
        charsetCandidates.add(Charset.forName("GB2312"));        // GB2312 (简体中文)
        charsetCandidates.add(StandardCharsets.UTF_8);           // UTF-8 (Linux/Mac默认)
        charsetCandidates.add(StandardCharsets.ISO_8859_1);      // ISO-8859-1 (西欧)
        charsetCandidates.add(Charset.forName("Big5"));           // Big5 (繁体中文)
        charsetCandidates.add(Charset.forName("UTF-16"));         // UTF-16
        charsetCandidates.add(Charset.forName("UTF-16LE"));       // UTF-16LE
        charsetCandidates.add(Charset.forName("UTF-16BE"));       // UTF-16BE

        // 尝试每个候选编码
        for (Charset charset : charsetCandidates) {
            try {
                if (isValidCharsetForZip(sourceFile, charset)) {
                    log.debug("检测到ZIP文件编码: {}", charset.name());
                    return charset;
                }
            } catch (Exception e) {
                log.debug("尝试编码 {} 失败: {}", charset.name(), e.getMessage());
            }
        }

        // 如果所有编码都失败，使用系统默认编码
        log.warn("无法确定ZIP文件编码，使用系统默认编码: {}", Charset.defaultCharset().name());
        return Charset.defaultCharset();
    }

    /**
     * 验证指定编码是否适用于该ZIP文件
     *
     * @param sourceFile ZIP文件路径
     * @param charset    要验证的字符编码
     * @return 如果编码有效返回true，否则返回false
     */
    private static boolean isValidCharsetForZip(String sourceFile, Charset charset) {
        try {
            ZipFile testZipFile = new ZipFile(sourceFile);
            testZipFile.setCharset(charset);

            List<FileHeader> headers = testZipFile.getFileHeaders();

            // 检查文件头是否可以正常读取
            for (FileHeader header : headers) {
                String fileName = header.getFileName();
                if (fileName != null) {
                    // 检查文件名是否包含明显的乱码特征
                    if (containsGarbledChars(fileName)) {
                        return false;
                    }

                    // 检查文件名是否包含有效的中文字符（如果有中文的话）
                    if (containsChineseChars(fileName) && !isValidChineseEncoding(fileName, charset)) {
                        return false;
                    }
                }
            }

            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 检查字符串是否包含明显的乱码特征字符（更宽松的标准）
     *
     * @param str 要检查的字符串
     * @return 如果包含明显乱码特征返回true
     */
    private static boolean containsObviousGarbledChars(String str) {
        if (str == null || str.isEmpty()) {
            return false;
        }

        // 只检查明显的乱码特征
        // 1. 连续的问号
        if (str.contains("??")) {
            return true;
        }

        // 2. 方块字符或其他不可见字符
        if (str.contains("")) {
            return true;
        }

        // 3. 检查是否包含过多的替换字符
        int replacementCharCount = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == '\uFFFD') { // Unicode替换字符
                replacementCharCount++;
            }
        }

        // 如果替换字符占比超过20%，认为是乱码（提高阈值）
        if ((double) replacementCharCount / str.length() > 0.2) {
            return true;
        }

        return false;
    }

    /**
     * 检查字符串是否包含乱码特征字符
     *
     * @param str 要检查的字符串
     * @return 如果包含乱码特征返回true
     */
    private static boolean containsGarbledChars(String str) {
        if (str == null || str.isEmpty()) {
            return false;
        }

        // 常见的乱码特征
        // 1. 连续的问号
        if (str.contains("??")) {
            return true;
        }

        // 2. 方块字符或其他不可见字符
        Pattern garbledPattern = Pattern.compile("[\ufffd\\u0000-\\u001F\\u007F]");
        if (garbledPattern.matcher(str).find()) {
            return true;
        }

        // 3. 路径中的问号是正常的，但其他位置的问号可能是乱码
        if (str.contains("?") && !str.contains("/")) {
            return true;
        }

        // 4. 检查是否包含过多的控制字符
        int controlCharCount = 0;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (Character.isISOControl(c) && c != '\t' && c != '\n' && c != '\r') {
                controlCharCount++;
            }
        }

        // 如果控制字符占比超过20%，认为是乱码
        if ((double) controlCharCount / str.length() > 0.2) {
            return true;
        }

        // 5. 检查是否包含过多的替换字符
        int replacementCharCount = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == '\uFFFD') { // Unicode替换字符
                replacementCharCount++;
            }
        }

        // 如果替换字符占比超过10%，认为是乱码
        if ((double) replacementCharCount / str.length() > 0.1) {
            return true;
        }

        // 6. 检查是否包含连续的不可打印字符
        int consecutiveUnprintableCount = 0;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (!isPrintableChar(c)) {
                consecutiveUnprintableCount++;
                if (consecutiveUnprintableCount > 2) {
                    return true;
                }
            } else {
                consecutiveUnprintableCount = 0;
            }
        }

        return false;
    }

    /**
     * 检查字符是否是可打印字符
     *
     * @param c 要检查的字符
     * @return 如果是可打印字符返回true
     */
    private static boolean isPrintableChar(char c) {
        // 基本拉丁字母、数字、标点符号
        if (c >= 32 && c <= 126) {
            return true;
        }

        // 中文字符范围
        if (c >= '\u4e00' && c <= '\u9fa5') {
            return true;
        }

        // 全角字符
        if (c >= '\uff00' && c <= '\uffef') {
            return true;
        }

        // 常用标点符号
        if (c == '。' || c == '，' || c == '、' || c == '；' || c == '：' ||
                c == '？' || c == '！' || c == '"' || c == '"' || c == '\'' || c == '\'' ||
                c == '（' || c == '）' || c == '【' || c == '】' || c == '《' || c == '》') {
            return true;
        }

        return false;
    }

    /**
     * 检查字符串是否包含中文字符
     *
     * @param str 要检查的字符串
     * @return 如果包含中文字符返回true
     */
    private static boolean containsChineseChars(String str) {
        boolean matches = str.matches(".*[\u4e00-\u9fa5]+.*");
        return matches;
    }

    /**
     * 验证中文字符在指定编码下是否有效
     *
     * @param str     包含中文的字符串
     * @param charset 字符编码
     * @return 如果编码有效返回true
     */
    private static boolean isValidChineseEncoding(String str, Charset charset) {
        try {
            // 尝试重新编码并检查结果
            byte[] bytes = str.getBytes(charset);
            String reencoded = new String(bytes, charset);

            // 检查重新编码后是否仍然包含中文字符
            boolean matches = reencoded.matches(".*[\u4e00-\u9fa5]+.*");
            return matches;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 必要时修复文件名编码
     *
     * @param fileName        原始文件名
     * @param detectedCharset 检测到的编码
     * @return 修复后的文件名
     */
    private static String fixFileNameIfNecessary(String fileName, Charset detectedCharset) {
        if (fileName == null || fileName.isEmpty()) {
            return fileName;
        }

        // 如果文件名不包含乱码，直接返回
        if (!containsGarbledChars(fileName)) {
            return fileName;
        }

        // 尝试多种修复方式
        String fixedName = fileName;

        // 1. 如果检测到的编码是GBK，但文件名看起来像是UTF-8被错误解码的结果
        if ("GBK".equalsIgnoreCase(detectedCharset.name()) ||
                "GB2312".equalsIgnoreCase(detectedCharset.name())) {

            try {
                // 尝试检测是否UTF-8被错误解码为ISO-8859-1
                byte[] bytes = fileName.getBytes(StandardCharsets.ISO_8859_1);
                if (isUtf8(bytes)) {
                    fixedName = new String(bytes, StandardCharsets.UTF_8);
                    if (!containsGarbledChars(fixedName)) {
//                        log.debug("修复文件名编码: '{}' -> '{}'", fileName, fixedName);
                        return fixedName;
                    }
                }
            } catch (Exception e) {
                log.debug("GBK编码修复失败: {}", e.getMessage());
            }
        }

        // 2. 如果检测到的编码是UTF-8，但文件名看起来像是GBK被错误解码的结果
        if ("UTF-8".equalsIgnoreCase(detectedCharset.name())) {
            try {
                // 尝试检测是否GBK被错误解码为ISO-8859-1
                byte[] bytes = fileName.getBytes(StandardCharsets.ISO_8859_1);
                fixedName = new String(bytes, Charset.forName("GBK"));
                if (!containsGarbledChars(fixedName)) {
//                    log.debug("修复文件名编码: '{}' -> '{}'", fileName, fixedName);
                    return fixedName;
                }
            } catch (Exception e) {
                log.debug("UTF-8编码修复失败: {}", e.getMessage());
            }
        }

        // 3. 尝试其他常见编码转换
        List<Charset> commonCharsets = new ArrayList<>();
        commonCharsets.add(StandardCharsets.UTF_8);
        commonCharsets.add(Charset.forName("GBK"));
        commonCharsets.add(Charset.forName("GB2312"));
        commonCharsets.add(StandardCharsets.ISO_8859_1);

        for (Charset sourceCharset : commonCharsets) {
            for (Charset targetCharset : commonCharsets) {
                if (sourceCharset.equals(targetCharset)) {
                    continue;
                }

                try {
                    byte[] bytes = fileName.getBytes(sourceCharset);
                    String testFixedName = new String(bytes, targetCharset);

                    if (!containsGarbledChars(testFixedName)) {
                        log.debug("编码转换成功: {} -> {} : '{}' -> '{}'",
                                sourceCharset.name(), targetCharset.name(), fileName, testFixedName);
                        return testFixedName;
                    }
                } catch (Exception e) {
                    // 忽略异常，继续尝试下一种转换
                }
            }
        }

        // 如果所有修复都失败，返回原始文件名
        log.debug("无法修复文件名编码: '{}'", fileName);
        return fileName;
    }

    /**
     * 判断字节数组是否为有效的UTF-8编码
     *
     * @param bytes 字节数组
     * @return 如果是有效的UTF-8返回true
     */
    private static boolean isUtf8(byte[] bytes) {
        try {
            CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
            decoder.onMalformedInput(CodingErrorAction.REPORT);
            decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
            decoder.decode(java.nio.ByteBuffer.wrap(bytes));
            return true;
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * 解压7Z文件（支持加密）
     */
    private static void decompress7zWithPassword(String sourceFile, Path targetDir, char[] password) throws IOException {
        SevenZFile sevenZFile;
        if (password != null && password.length > 0) {
            sevenZFile = new SevenZFile(new File(sourceFile), password);
        } else {
            sevenZFile = new SevenZFile(new File(sourceFile));
        }

        try {
            SevenZArchiveEntry entry;
            while ((entry = sevenZFile.getNextEntry()) != null) {
                if (entry.isDirectory()) continue;

                Path entryPath = targetDir.resolve(entry.getName());
                safeCreateFile(entryPath, false);

                try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(entryPath))) {
                    byte[] buffer = new byte[8192];
                    int bytesRead;
                    while ((bytesRead = sevenZFile.read(buffer)) != -1) {
                        os.write(buffer, 0, bytesRead);
                    }
                }
            }
        } finally {
            sevenZFile.close();
        }
    }

    // --------------- 以下为非加密格式的解压方法 --------------- 

    // 通用归档格式解压（TAR）
    private static void decompressArchive(String sourceFile, Path targetDir) throws IOException, ArchiveException {
        try (InputStream is = Files.newInputStream(Paths.get(sourceFile)); ArchiveInputStream ais = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.TAR, is)) {

            ArchiveEntry entry;
            while ((entry = ais.getNextEntry()) != null) {
                if (!ais.canReadEntryData(entry)) continue;
                Path entryPath = targetDir.resolve(entry.getName());
                safeCreateFile(entryPath, entry.isDirectory());
                if (!entry.isDirectory()) {
                    Files.copy(ais, entryPath, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }

    // GZIP解压（单文件）
    private static void decompressGzip(String sourceFile, Path targetDir) throws IOException, CompressorException {
        Path targetFile = targetDir.resolve(FilenameUtils.getBaseName(sourceFile));
        try (InputStream fi = Files.newInputStream(Paths.get(sourceFile)); CompressorInputStream cis = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.GZIP, fi)) {
            Files.copy(cis, targetFile, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    // BZIP2解压（单文件）
    private static void decompressBzip2(String sourceFile, Path targetDir) throws IOException, CompressorException {
        Path targetFile = targetDir.resolve(FilenameUtils.getBaseName(sourceFile));
        try (InputStream fi = Files.newInputStream(Paths.get(sourceFile)); CompressorInputStream cis = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.BZIP2, fi)) {
            Files.copy(cis, targetFile, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    // TAR.GZ解压
    private static void decompressTarGz(String sourceFile, Path targetDir) throws IOException, CompressorException, ArchiveException {
        try (InputStream fi = Files.newInputStream(Paths.get(sourceFile)); CompressorInputStream cis = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.GZIP, fi); ArchiveInputStream ais = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.TAR, cis)) {

            ArchiveEntry entry;
            while ((entry = ais.getNextEntry()) != null) {
                if (!ais.canReadEntryData(entry)) continue;
                Path entryPath = targetDir.resolve(entry.getName());
                safeCreateFile(entryPath, entry.isDirectory());
                if (!entry.isDirectory()) {
                    Files.copy(ais, entryPath, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }

    // 安全创建文件（防止路径穿越）
    private static void safeCreateFile(Path path, boolean isDirectory) throws IOException {
        Path normalized = path.normalize();
        Path parentDir = normalized.getParent();

        if (parentDir == null || !Files.exists(parentDir)) {
            Files.createDirectories(parentDir != null ? parentDir : path.getRoot());
        }

        if (isDirectory) {
            Files.createDirectories(normalized);
        } else {
            Files.createDirectories(normalized.getParent());
            if (!Files.exists(normalized)) {
                Files.createFile(normalized);
            }
        }
    }

    /**
     * 解压压缩包并返回解压后的文件/文件夹列表
     *
     * @param zipFile 压缩包文件对象
     * @param destDir 解压目标目录
     * @return 解压后生成的文件/文件夹列表（直接位于目标目录下）
     * @throws IOException 如果解压过程出错
     */
    public static List<File> unzipAndGetContents(String zipFile, String destDir, char[] password) throws IOException {
        // 1. 记录解压前目标目录下的所有文件和文件夹
        File destDirFile = new File(destDir);
        Set<String> existingFiles = new HashSet<>();
        if (destDirFile.exists() && destDirFile.isDirectory()) {
            try (Stream<Path> stream = Files.list(destDirFile.toPath())) {
                existingFiles = stream.map(Path::getFileName).map(Path::toString).collect(Collectors.toSet());
            }
        }

        // 2. 执行解压操作（此处省略具体解压实现）
        recursionDecompress(zipFile, password);

        // 3. 获取解压后新增的内容
        List<File> newContents = new ArrayList<>();
        File[] filesAfterUnzip = destDirFile.listFiles();
        if (filesAfterUnzip != null) {
            for (File file : filesAfterUnzip) {
                // 排除解压前已存在的文件和压缩包本身
                if (!existingFiles.contains(file.getName()) && !file.equals(zipFile)) {
                    newContents.add(file);
                }
            }
        }

        return newContents;
    }

    /**
     * 将文件夹压缩成ZIP文件
     *
     * @param sourceDir   要压缩的文件夹路径
     * @param zipFilePath 压缩后的ZIP文件路径
     * @return 压缩后的文件对象，如果压缩失败返回null
     */
    public static boolean zip(String sourceDir, String zipFilePath) {
        File sourceFolder = new File(sourceDir);
        if (!sourceFolder.exists() || !sourceFolder.isDirectory()) {
            log.error("源文件夹不存在或不是目录: {}", sourceDir);
            return false;
        }

        try {
            // 确保目标目录存在
            File zipFile = new File(zipFilePath);
            File parentDir = zipFile.getParentFile();
            if (parentDir != null && !parentDir.exists()) {
                Files.createDirectories(parentDir.toPath());
            }

            // 创建压缩文件
            ZipFile zip = new ZipFile(zipFilePath);

            // 配置压缩参数
            ZipParameters parameters = new ZipParameters();
            parameters.setCompressionMethod(CompressionMethod.DEFLATE);
            parameters.setCompressionLevel(CompressionLevel.NORMAL);

            // 添加文件夹到压缩包
            zip.addFolder(sourceFolder, parameters);

            log.info("文件夹：{} 压缩成功为 {}", sourceDir, zipFilePath);
            return true;
        } catch (Exception e) {
            log.error("文件夹压缩失败，sourceDir: {}, error: {}", sourceDir, e.getMessage(), e);
            return false;
        }
    }

    /**
     * 尝试多种编码方式修复文件名
     *
     * @param fileName        原始文件名
     * @param detectedCharset 检测到的编码
     * @return 修复后的文件名
     */
    private static String tryMultipleEncodingFix(String fileName, Charset detectedCharset) {
        // 尝试多种编码转换方式
        List<Charset> encodingFixes = new ArrayList<>();

        // 添加常见的编码转换方式
        encodingFixes.add(StandardCharsets.UTF_8);
        encodingFixes.add(Charset.forName("GBK"));
        encodingFixes.add(Charset.forName("GB2312"));
        encodingFixes.add(StandardCharsets.ISO_8859_1);
        encodingFixes.add(Charset.forName("Big5")); // 繁体中文

        // 尝试每种编码转换
        for (Charset sourceCharset : encodingFixes) {
            for (Charset targetCharset : encodingFixes) {
                if (sourceCharset.equals(targetCharset)) {
                    continue;
                }

                try {
                    // 尝试从源编码转换为目标编码
                    byte[] bytes = fileName.getBytes(sourceCharset);
                    String fixedName = new String(bytes, targetCharset);

                    // 检查修复后的文件名是否有效
                    if (!containsGarbledChars(fixedName) &&
                            (!containsChineseChars(fixedName) || isValidChineseEncoding(fixedName, targetCharset))) {
                        log.debug("编码转换成功: {} -> {} : '{}'", sourceCharset.name(), targetCharset.name(), fixedName);
                        return fixedName;
                    }
                } catch (Exception e) {
                    // 忽略异常，继续尝试下一种转换
                }
            }
        }

        // 如果所有转换都失败，尝试更复杂的修复方式
        return tryAdvancedEncodingFix(fileName);
    }

    /**
     * 尝试高级编码修复方式
     *
     * @param fileName 原始文件名
     * @return 修复后的文件名
     */
    private static String tryAdvancedEncodingFix(String fileName) {
        try {
            // 尝试修复常见的编码问题

            // 1. UTF-8 被错误解码为 ISO-8859-1
            byte[] bytes = fileName.getBytes(StandardCharsets.ISO_8859_1);
            if (isUtf8(bytes)) {
                String fixedName = new String(bytes, StandardCharsets.UTF_8);
                if (!containsGarbledChars(fixedName)) {
                    log.debug("高级修复成功: ISO-8859-1 -> UTF-8 : '{}'", fixedName);
                    return fixedName;
                }
            }

            // 2. GBK 被错误解码为 ISO-8859-1
            bytes = fileName.getBytes(StandardCharsets.ISO_8859_1);
            String fixedName = new String(bytes, Charset.forName("GBK"));
            if (!containsGarbledChars(fixedName) &&
                    (!containsChineseChars(fixedName) || isValidChineseEncoding(fixedName, Charset.forName("GBK")))) {
                log.debug("高级修复成功: ISO-8859-1 -> GBK : '{}'", fixedName);
                return fixedName;
            }

            // 3. 尝试修复双字节编码问题
            if (fileName.length() > 1) {
                // 尝试重新组合字节
                byte[] originalBytes = fileName.getBytes(StandardCharsets.ISO_8859_1);
                byte[] newBytes = new byte[originalBytes.length * 2];

                for (int i = 0; i < originalBytes.length; i++) {
                    newBytes[i * 2] = 0;
                    newBytes[i * 2 + 1] = originalBytes[i];
                }

                fixedName = new String(newBytes, StandardCharsets.UTF_16LE);
                if (!containsGarbledChars(fixedName)) {
                    log.debug("高级修复成功: 双字节编码修复 : '{}'", fixedName);
                    return fixedName;
                }
            }

        } catch (Exception e) {
            log.debug("高级编码修复失败: {}", e.getMessage());
        }

        // 如果所有修复都失败，返回原始文件名
        return fileName;
    }

}